﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using Lm.Common.ExtensionMethods;
using System.Threading;
using System.IO;

namespace Lm.Common.Net
{
    public class Server
    {
        public Dictionary<Thread, TcpClient> ServiceThreadList { get; set; }

        public IPAddress ListenIp { get; set; }
        public int ListenPort { get; set; }

        protected Server()
        { }

        public Server(string listenIp, int listenPort)
        {
            this.ServiceThreadList = new Dictionary<Thread, TcpClient>();
            this.ListenIp = IPAddress.Parse(listenIp);
            this.ListenPort = listenPort;
        }

        public void Start()
        {
            try
            {
                var tcpListener = new TcpListener(this.ListenIp, this.ListenPort);
                tcpListener.Start();
                while (true)
                {
                    var tcpClient = tcpListener.AcceptTcpClient();
                    var t = new Thread(new ParameterizedThreadStart(this.Work));
                    t.IsBackground = true;
                    lock (this.ServiceThreadList)
                    {
                        this.ServiceThreadList.Add(t, tcpClient);
                    }
                    t.Start(tcpClient);
                }
            }
            catch (Exception ee)
            {
                ApplicationContext.LogWriter.Write(ee);
            }
        }

        private void Work(object obj)
        {
            try
            {
                var tcpClient = obj as TcpClient;
                var safeStream = tcpClient.GetEncryptedStream();
                safeStream.WaitEncrypt();
                while (true)
                {
                    try
                    {
                        var requestBuf = safeStream.ReadBlock();                        
                        var requestMessage = new Serialization.BinaryFormatter()
                            .Deserialize<object>
                            (requestBuf);
                        var serviceAssemblyList = requestMessage.GetType().Assembly
                            .GetCustomAttributes(typeof(TargetServiceAssemblyAttribute), true);
                        var serviceAssembly = serviceAssemblyList.Single() as TargetServiceAssemblyAttribute;
                        var serviceClass = System.Reflection.Assembly.Load(serviceAssembly.TargetServiceAssemblyName)
                            .CreateInstance(serviceAssembly.TargetServiceClassName) as IRequestHandler;
                        var responseMessage = serviceClass.Handle(requestMessage);
                        var responseBuf = new Serialization.BinaryFormatter().Serialize(responseMessage);
                        safeStream.WriteBlock(responseBuf);
                    }
                    catch (IOException ee)
                    { throw ee; }
                    catch (SocketException ee)
                    { throw ee; }
                    catch (ObjectDisposedException ee)
                    { throw ee; }
                    catch (Exception ee)
                    {
                        ApplicationContext.LogWriter.Write(ee);
                        var responseBuf = new Serialization.BinaryFormatter()
                            .Serialize(
                                    new Lm.Common.Net.ErrResponseMessage()
                                    {
                                        ErrMessage = ee.Message,
                                    });
                        safeStream.WriteBlock(responseBuf);
                    }
                }

            }
            catch (IOException ee)
            { }
            catch (SocketException ee)
            { }
            catch (ObjectDisposedException ee)
            { }
            catch (Exception ee)
            {
                ApplicationContext.LogWriter.Write(ee);
            }
            finally
            {
                lock (this.ServiceThreadList)
                {
                    TcpClient client;
                    this.ServiceThreadList.TryGetValue(Thread.CurrentThread, out client);
                    if (client != null)
                    { client.Close(); }
                    this.ServiceThreadList.Remove(Thread.CurrentThread);
                }
            }
        }
    }
}
